/*
 * PhotonRTOS础光实时操作系统
 *
 * Copyright (C) 2022, 2023 国科础石(重庆)软件有限公司
 *
 * 作者: Baoyou Xie <xiebaoyou@kernelsoft.com>
 *
 * License terms: GNU General Public License (GPL) version 3
 *
 */

#include <photon/kernel.h>
#include <photon/sched.h>
#include <photon/sections.h>
#include <asm/internel.h>

#ifdef CONFIG_KALLSYMS

#define ASM_DUMP_BACKTRACE				\
	asm("mov %0, sp" : "=r" (stack) : : "cc");

static bool disassembly_ins_is_bl_blx(uintptr_t addr) {
	uintptr_t ins1 = *((uintptr_t  *)addr);

#define BLA1_INX_MASK		0x0F000000U
#define BLA1_INX		0x0B000000U
#define BLA2_INX_MASK		0xFE000000U
#define BLA2_INX		0xFA000000U
#define BLX_INX_MASK		0x0FF000F0U
#define BLX_INX			0x01200030U

	if ( (ins1 & BLA1_INX_MASK) == BLA1_INX )
	{
		return true;
	}
	if ( (ins1 & BLA2_INX_MASK) == BLA2_INX )
	{
		return true;
	}
	else if ((ins1 & BLX_INX_MASK) == BLX_INX)
	{
		return true;
	}
	else
	{
		return false;
	}
}
static int32_t c_backtrace_call_stack(uintptr_t *stack,				\
	uintptr_t *where, uintptr_t  sp_max)
{
	uintptr_t addr;
	for (; *stack < sp_max; *stack += sizeof(size_t))
	{
		addr = *((uintptr_t *) *stack) - sizeof(size_t);
		if ((addr % 4) != 0) {
			continue;
		}
		addr = *((uintptr_t *) *stack);
		if ((addr >= (uintptr_t )(kernel_text_start + sizeof(size_t)))	\
			&& (addr <= (uintptr_t )kernel_text_end)		\
			&& disassembly_ins_is_bl_blx(addr - sizeof(size_t)))
		{
			*where = addr - sizeof(size_t);
			return 0;
		}
	}
	return -1;
}

static void dump_backtrace_entry(uintptr_t where, uintptr_t stack)
{
	printk("  [<%p>] %pS\n", (void *) where, (void *) where);
}

static void dump_backtrace(void *regs, struct task_desc *tsk)
{
	uintptr_t stack = 0;
	uintptr_t high;

	pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);

	if (!tsk) {
		tsk = current;
	}

	if (regs != NULL) {
		/* 暂不支持regs，目前都是current */
	} else if (tsk != current) {
		stack = thread_saved_fp(tsk);
	} else {
		ASM_DUMP_BACKTRACE;
	}

	high = ALIGN(stack, PROCESS_STACK_SIZE);
	high -= 0x18;
	if (stack > high) {
		return;
	}

	pr_emerg("Call trace:\n");
	while (true) {
		uintptr_t where;
		int32_t ret;

		ret = c_backtrace_call_stack(&stack, &where, high);
		if (ret < 0) {
			break;
		}
		dump_backtrace_entry(where, 0);
		stack += 4;
	}

}

void dump_task_stack(struct task_desc *tsk, uintptr_t *sp)
{
	dump_backtrace(NULL, tsk);
	barrier();
}
#else
void dump_task_stack(struct task_desc *tsk, uintptr_t *sp)
{
}
#endif

asmlinkage void __div0(void);
asmlinkage void __div0(void)
{
	pr_err("Division by zero in kernel.\n");
	dump_task_stack(NULL, NULL);
}

